'use strict'

// <--    还原前    -->
var arr = "3|0|1|2|4".split("|");
var cnt = 0;

while (true) {
  switch (arr[cnt++]) {
    case "0":
      console.log("This is case-block 0");
      console.log("This is case-block 0+");
      continue;

    case "1":
      console.log("This is case-block 1");
      continue;

    case "2":
      console.log("This is case-block 2");
      continue;

    case "3":
      console.log("This is case-block 3");
      continue;

    case "4":
      console.log("This is case-block 4");
      continue;
  }

  break;
}
/**
 * <--    特点    -->
 * 1. 本身就是一个顺序执行，但是通过对每个语句编号，把编号放到一个数组中，通过while-switch遍历数组，以此执行语句。
 * 2. 在switch-case语句块中可以随便修改编号-语句对顺序，打乱代码，以此混淆
 * 3. 按数组编号顺序，将哥case语句提出来，还原成顺序结构
 * 
 * <--    解决办法    -->
 * 1. while(true){switch;break;}, swith(memberexp[update]), case:[stms];continue;这些都是检查while-switch控制流的特征
 * 2. 数组编号顺序，就是case编号语句的执行顺序，依次将这些语句存入插入到whileStatement之前，最终删除WhileStatement
 * 
 * <--    冗余判断    -->
 * 1. while语句的条件恒为真，其语句块包含2部分，其一为switch语句，其二为break语句；
 * 2. switch语句判别式为成员表达式arr[i]，其成员表达式为自增表达式；
 * 3. case语句，最后不一定为continue，因为有时候为达到混淆的目的，故意添加一些无用case，即该数组编号不存在于数组中
 * 
 **/

// <--    还原后    -->
console.log("This is case-block 3");
console.log("This is case-block 0");
console.log("This is case-block 0+");
console.log("This is case-block 1");
console.log("This is case-block 2");
console.log("This is case-block 4");
// <----------------->

const {parse} = require('@babel/parser');
const traverse = require('@babel/traverse').default;
const t = require('@babel/types');
const generator = require('@babel/generator').default;


let xcode = `var arr = "3|0|1|2|4".split("|");
var cnt = 0;

while (true) {
  switch (arr[cnt++]) {
    case "0":
      console.log("This is case-block 0");
      console.log("This is case-block 0+");
      continue;

    case "1":
      console.log("This is case-block 1");
      continue;

    case "2":
      console.log("This is case-block 2");
      continue;

    case "3":
      console.log("This is case-block 3");
      continue;

    case "4":
      console.log("This is case-block 4");
      continue;
  }

  break;
}`;

const visitor = {
    WhileStatement:{
        enter:function(path){
            if (!t.isBooleanLiteral(path.get('test').node, {value: true})
                  || path.get('body.body').length != 2
                  || !t.isSwitchStatement(path.get('body.body.0').node)
                  || !t.isBreakStatement(path.get('body.body.1').node)) {
                return;
            }

            let switchStmt = path.get('body.body.0');

            if (!t.isMemberExpression(switchStmt.get('discriminant').node)
                  || !t.isUpdateExpression(switchStmt.get('discriminant.property').node, {prefix:false, operator:'++'})){
                      return;
            }

            console.log('check bingo');

            let idfArrPath = switchStmt.scope.getBinding(switchStmt.get('discriminant.object').node.name).path;
            let idfArr = idfArrPath.get('init').evaluate().value;
            console.log(idfArr);
            
            let whileStmt = path;
            for (let idf of idfArr){
                switchStmt.traverse({
                    SwitchCase:{
                        enter:function(path){
                            if(path.get('test').node.value == idf){
                                for (let stmt of path.get('consequent')){
                                    if(!t.isContinueStatement(stmt.node)){
                                        whileStmt.insertBefore(stmt.node);
                                    }
                                }
                            }
                        }
                    },
                })
            }
            console.log('order bingo');
            let program = path.findParent(path => t.isProgram(path.node));
            path.remove();

            program.scope.crawl();
            program.traverse({
                VariableDeclarator:{
                    enter: function(path){
                        let binding = path.scope.getBinding(path.get('id').node.name);
                        if (binding.constant){
                            path.remove();
                        }
                    },
                },
            });

            program.scope.crawl();
        },
    },
}

let ast = parse(xcode);
traverse(ast, visitor);
let {code} = generator(ast, {retainLines: false});
console.log(code);
